using System;
using System.Activities;
using Microsoft.Xrm.Sdk.Workflow;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Crm.Sdk.Messages;

namespace CRM2011WorkflowUtilities
{
    public class Share : CodeActivity
    {
        private static int shareAlways = 667640000;
        private static int shareIfAllowed = 667640001;

        protected override void Execute(CodeActivityContext executionContext)
        {
            try
            {
                //Create the context and tracing service
                IExecutionContext context = executionContext.GetExtension<IExecutionContext>();
                IOrganizationServiceFactory serviceFactory = executionContext.GetExtension<IOrganizationServiceFactory>();
                IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
                ITracingService tracer = executionContext.GetExtension<ITracingService>();

                EntityReference callingUser = new EntityReference("systemuser", context.UserId);

                EntityReference user = User.Get(executionContext);
                EntityReference team = Team.Get(executionContext);
                if (user == null && team == null)
                {
                    Helpers.Throw("Must specify either user or team to share the record with");
                }

                if (PrimaryEntity.Get(executionContext))
                {
                    tracer.Trace("Sharing process primary entity");
                    ShareWithUserTeam(new EntityReference(context.PrimaryEntityName, context.PrimaryEntityId), executionContext, user, team, callingUser, service);
                }
                else
                {
                    string relatedAttribute = RelatedAttributeName.Get(executionContext);
                    if (string.IsNullOrEmpty(relatedAttribute))
                    {
                        Helpers.Throw("If sharing related entity, related attribute name must be specified in the process share step configuration");
                    }

                    // Retrieve primary entity with the required attribute
                    tracer.Trace("Retrieving process primary entity");
                    Entity primaryEntity = service.Retrieve(context.PrimaryEntityName, context.PrimaryEntityId, new ColumnSet(relatedAttribute));

                    if (primaryEntity.Contains(relatedAttribute))
                    {
                        EntityReference reference = primaryEntity[relatedAttribute] as EntityReference;
                        if (reference == null)
                        {
                            Helpers.Throw(string.Format("The attribute {0} on entity {1} is expected to be of EntityReference type",
                                relatedAttribute, context.PrimaryEntityName));
                        }

                        tracer.Trace("Sharing entity related to primary entity by attribute " + relatedAttribute);
                        ShareWithUserTeam(reference, executionContext, user, team, callingUser, service);
                    }
                }

            }
            catch (Exception ex)
            {
               Helpers.Throw(String.Format("An error occurred in the {0} plug-in.",
                       this.GetType().ToString()),
                     ex);
            }

            return;
        }

        private AccessRights ComputeAccessRights(CodeActivityContext executionContext, EntityReference target, EntityReference callingUser, IOrganizationService service)
        {
            // Retrieve the privileges that the current user has on the record
            RetrievePrincipalAccessRequest retrieveAccessRightsReq = new RetrievePrincipalAccessRequest()
            {
                Target = target,
                Principal = callingUser,
            };
            RetrievePrincipalAccessResponse retrieveAccessRightsResp = (RetrievePrincipalAccessResponse)service.Execute(retrieveAccessRightsReq);

            AccessRights accessRights = AccessRights.None;
            if ((ReadAccess.Get(executionContext).Value == shareIfAllowed && (retrieveAccessRightsResp.AccessRights & AccessRights.ReadAccess) != 0) ||
                ReadAccess.Get(executionContext).Value == shareAlways)
            {
                accessRights |= AccessRights.ReadAccess;
            }
            if ((WriteAccess.Get(executionContext).Value == shareIfAllowed && (retrieveAccessRightsResp.AccessRights & AccessRights.WriteAccess) != 0) ||
                WriteAccess.Get(executionContext).Value == shareAlways)
            {
                accessRights |= AccessRights.WriteAccess;
            }
            if ((DeleteAccess.Get(executionContext).Value == shareIfAllowed && (retrieveAccessRightsResp.AccessRights & AccessRights.DeleteAccess) != 0) ||
                DeleteAccess.Get(executionContext).Value == shareAlways)
            {
                accessRights |= AccessRights.DeleteAccess;
            }
            if ((AssignAccess.Get(executionContext).Value == shareIfAllowed && (retrieveAccessRightsResp.AccessRights & AccessRights.AssignAccess) != 0) ||
                AssignAccess.Get(executionContext).Value == shareAlways)
            {
                accessRights |= AccessRights.AssignAccess;
            }
            if ((ShareAccess.Get(executionContext).Value == shareIfAllowed && (retrieveAccessRightsResp.AccessRights & AccessRights.ShareAccess) != 0) ||
                 ShareAccess.Get(executionContext).Value == shareAlways)
            {
                accessRights |= AccessRights.ShareAccess;
            }
            if ((AppendAccess.Get(executionContext).Value == shareIfAllowed && (retrieveAccessRightsResp.AccessRights & AccessRights.AppendAccess) != 0) ||
                 AppendAccess.Get(executionContext).Value == shareAlways)
            {
                accessRights |= AccessRights.AppendAccess;
            }
            if ((AppendToAccess.Get(executionContext).Value == shareIfAllowed && (retrieveAccessRightsResp.AccessRights & AccessRights.AppendToAccess) != 0) ||
                  AppendToAccess.Get(executionContext).Value == shareAlways)
            {
                accessRights |= AccessRights.AppendToAccess;
            }
            return accessRights;
        }

        private void ShareWithUserTeam(EntityReference target, CodeActivityContext executionContext, EntityReference user, EntityReference team, EntityReference callingUser, IOrganizationService service)
        {
            AccessRights accessRights = ComputeAccessRights(executionContext, target, callingUser, service);
            if (user != null)
            {
                ShareRecord(target, user, accessRights, service);
            }
            if (team != null)
            {
                ShareRecord(target, team, accessRights, service);
            }
        }

        private void ShareRecord(EntityReference target, EntityReference principal, AccessRights rights, IOrganizationService service)
        {
            GrantAccessRequest grantRequest = new GrantAccessRequest()
            {
                Target = target,
                PrincipalAccess = new PrincipalAccess()
                {
                    Principal = principal,
                    AccessMask = rights
                }
            };
            service.Execute(grantRequest);
        }

        #region Input Parameters

        [RequiredArgument]
        [Input("Share primary entity (True) or related entity (False)")]
        public InArgument<bool> PrimaryEntity { get; set; }

        [Input("If sharing related entity, specify attribute name to get the related entity")]
        public InArgument<string> RelatedAttributeName { get; set; }

        [Input("User to share the record with")]
        [ReferenceTarget("systemuser")]
        public InArgument<EntityReference> User { get; set; }

        [Input("Team to share the record with")]
        [ReferenceTarget("team")]
        public InArgument<EntityReference> Team { get; set; }

        [RequiredArgument]
        [Input("Read Access")]
        [AttributeTarget("gonz_optionsets", "gonz_shareoptionset")]
        public InArgument<OptionSetValue> ReadAccess { get; set; }

        [RequiredArgument]
        [Input("Write Access")]
        [AttributeTarget("gonz_optionsets", "gonz_shareoptionset")]
        public InArgument<OptionSetValue> WriteAccess { get; set; }

        [RequiredArgument]
        [Input("Delete Access")]
        [AttributeTarget("gonz_optionsets", "gonz_shareoptionset")]
        public InArgument<OptionSetValue> DeleteAccess { get; set; }

        [RequiredArgument]
        [Input("Assign Access")]
        [AttributeTarget("gonz_optionsets", "gonz_shareoptionset")]
        public InArgument<OptionSetValue> AssignAccess { get; set; }

        [RequiredArgument]
        [Input("Share Access")]
        [AttributeTarget("gonz_optionsets", "gonz_shareoptionset")]
        public InArgument<OptionSetValue> ShareAccess { get; set; }

        [RequiredArgument]
        [Input("Append Access")]
        [AttributeTarget("gonz_optionsets", "gonz_shareoptionset")]
        public InArgument<OptionSetValue> AppendAccess { get; set; }

        [RequiredArgument]
        [Input("Append To Access")]
        [AttributeTarget("gonz_optionsets", "gonz_shareoptionset")]
        public InArgument<OptionSetValue> AppendToAccess { get; set; }
        #endregion

        #region Output Parameters
        #endregion
    }
}
